spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import type { Metadata } from "next";2import Link from "next/link";3import { EventRow } from "@/components/market/event-row";4import { InstrumentTable } from "@/components/market/instrument-table";5import { Empty, Page, PageHeader, Section, Stat } from "@/components/ui/section";6import { StatusBadge } from "@/components/ui/status-badge";7import { api } from "@/lib/api";8import type { Breadth, Country, Exchange, ExchangeStatus, InstrumentWithQuote, MarketEvent } from "@/lib/types";910interface Detail {11 country: Country;12 exchanges: Array<Exchange & { status: ExchangeStatus }>;13 indices: InstrumentWithQuote[];14 rates: InstrumentWithQuote[];15 fx: InstrumentWithQuote[];16 equities: InstrumentWithQuote[];17 breadth: Breadth | null;18 events: MarketEvent[];19 instruments_tracked: number;20}2122export const dynamic = "force-dynamic";2324export async function generateMetadata({ params }: { params: Promise<{ code: string }> }): Promise<Metadata> {25 const { code } = await params;26 try {27 const d = await api<Detail>(`/v1/countries/${encodeURIComponent(code)}`);28 return { title: `${d.country.name} — markets`, description: `${d.country.name}: exchanges, indices, rates, ${d.country.currency ?? "currency"} pairs and the equities Market Atlas observes.`, alternates: { canonical: `/countries/${d.country.code}` } };29 } catch {30 return { title: "Country" };31 }32}3334export default async function CountryPage({ params }: { params: Promise<{ code: string }> }) {35 const { code } = await params;36 const d = await api<Detail>(`/v1/countries/${encodeURIComponent(code)}`);37 const c = d.country;38 const open = d.exchanges.filter((e) => e.status.state === "OPEN").length;39 return (40 <Page wide>41 <PageHeader42 kicker={43 <Link href="/countries" className="hover:text-ink">44 Countries · {c.region}45 </Link>46 }47 title={c.name}48 lead={`${c.currency ? `Currency ${c.currency} · ` : ""}${d.exchanges.length} venue${d.exchanges.length === 1 ? "" : "s"} in the atlas · ${d.instruments_tracked.toLocaleString("en-US")} instruments tracked.`}49 />50 <div className="grid grid-cols-2 gap-4 rounded-md border border-rule bg-surface px-4 py-4 sm:grid-cols-4">51 <Stat label="Venues open" value={`${open} / ${d.exchanges.length}`} />52 <Stat label="Indices" value={d.indices.length} sub="with canonical level" />53 <Stat label="Rates & yields" value={d.rates.length} sub="official series" />54 <Stat label="Equity breadth" value={d.breadth ? `${d.breadth.advancers} ▲ ${d.breadth.decliners} ▼` : "—"} sub={d.breadth ? `${d.breadth.instruments} quoted` : "no quoted equities"} />55 </div>56 <Section title="Exchanges" href="/exchanges">57 {d.exchanges.length ? (58 <ul className="grid grid-cols-1 [&>*]:min-w-0 gap-2 sm:grid-cols-2 lg:grid-cols-3">59 {d.exchanges.map((e) => (60 <li key={e.id} className="rounded-md border border-rule bg-surface px-3 py-2.5">61 <Link href={`/exchanges/${e.id}`} className="font-medium hover:underline">62 {e.name}63 </Link>64 <div className="mt-1 flex items-center gap-2 text-xs text-ink-3">65 <StatusBadge status={e.status.state} /> <span className="mono">{e.status.localTime}</span> local · {e.mic ?? e.id}66 </div>67 </li>68 ))}69 </ul>70 ) : (71 <Empty>No venue of {c.name} is in the atlas yet.</Empty>72 )}73 </Section>74 {d.indices.length > 0 && (75 <Section title="Indices" href="/indices">76 <InstrumentTable rows={d.indices} compact showExchange={false} />77 </Section>78 )}79 <div className="grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-2">80 <Section title="Rates & yields" href="/rates" hint="percent · official">81 <InstrumentTable rows={d.rates} compact showExchange={false} defaultSort="symbol" emptyText={`No official rate series for ${c.name} yet.`} />82 </Section>83 <Section title={`${c.currency ?? "Currency"} pairs`} href="/forex" hint="reference rates">84 <InstrumentTable rows={d.fx} compact showExchange={false} defaultSort="symbol" emptyText="No currency pairs yet." />85 </Section>86 </div>87 <Section title="Equities" href={`/stocks`} hint="most active among tracked names">88 <InstrumentTable rows={d.equities} compact emptyText={`No quoted equities for ${c.name} yet — the instrument master lists them but no source publishes their prices.`} />89 </Section>90 <Section title="Events">91 {d.events.length ? (92 <ul className="rounded-md border border-rule bg-surface px-3">93 {d.events.map((e) => (94 <EventRow key={e.id} e={e} />95 ))}96 </ul>97 ) : (98 <Empty>No events touching {c.name} instruments yet.</Empty>99 )}100 </Section>101 </Page>102 );103}104